﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;

namespace RPC {
	public class RPCManager {
		#region Nested
		private static class CRC16 {
			#region Fields
			private const ushort polynomial = 0xA001;
			private static ushort[] table = new ushort[256];
			#endregion
			#region Constructor
			static CRC16() {
				ushort value;
				ushort temp;
				for (ushort i = 0; i < table.Length; ++i) {
					value = 0;
					temp = i;
					for (byte j = 0; j < 8; ++j) {
						if (((value ^ temp) & 0x0001) != 0) {
							value = (ushort)((value >> 1) ^ polynomial);
						} else {
							value >>= 1;
						}
						temp >>= 1;
					}
					table[i] = value;
				}

			}
			#endregion
			#region Public Voids
			public static ushort ComputeChecksum(Byte[] data) {
				return ComputeChecksum(data, 0, data.Length);
			}
			public static ushort ComputeChecksum(Byte[] bytes, int offset, Int32 count) {
				ushort crc = 0;
				for (int i = offset; i < count; ++i) {
					byte index = (byte)(crc ^ bytes[i]);
					crc = (ushort)((crc >> 8) ^ table[index]);
				}
				return crc;
			}
			#endregion
		}
		private static class CRC32 {
			#region Fields
			private static uint[] table;
			#endregion
			#region Constructor
			static CRC32() {
				uint poly = 0xedb88320;
				table = new uint[256];
				uint temp = 0;
				for (uint i = 0; i < table.Length; ++i) {
					temp = i;
					for (int j = 8; j > 0; --j) {
						if ((temp & 1) == 1) {
							temp = (uint)((temp >> 1) ^ poly);
						} else {
							temp >>= 1;
						}
					}
					table[i] = temp;
				}
			}
			#endregion
			#region Public Voids
			public static uint ComputeChecksum(Byte[] data) {
				return ComputeChecksum(data, 0, data.Length);
			}
			public static uint ComputeChecksum(byte[] data, Int32 offset, Int32 count) {
				uint crc = 0xffffffff;
				for (int i = offset; i < count; ++i) {
					byte index = (byte)(((crc) & 0xff) ^ data[i]);
					crc = (uint)((crc >> 8) ^ table[index]);
				}
				return ~crc;
			}
			#endregion
		}
		#endregion
		#region Const
		public const Int32 BufferSize = 1024;
		public const Int32 MessageHeaderLength = 14;
		#endregion
		#region Fields
		private Dictionary<UInt16, Type> _types = new Dictionary<UInt16, Type>();
		private Dictionary<UInt16, Delegate> _events = new Dictionary<UInt16, Delegate>();
		private RPCInvoke _eventReceived;
		#endregion
		#region Protected
		/// <summary>
		/// Validates header of the message.
		/// </summary>
		/// <param name="data">The data.</param>
		/// <returns>True, if it validates</returns>
		protected bool ValidateHeader(Byte[] data) {
			if (data != null && data.Length == MessageHeaderLength) {
				ushort checksum = CRC16.ComputeChecksum(data, 0, MessageHeaderLength - 2);
				ushort provided = BitConverter.ToUInt16(data, MessageHeaderLength - 2);

				return (checksum == provided);
			}
			return false;
		}
		protected void WriteHeader(Stream stream, UInt16 id, RPCBody body, RPCFormat format, Int32 length, UInt32 checksum) {
			/*   Header 
			 *    0-2 - Message ID
			 *    2-3 - Format
			 *    3-4 - Body
			 *    4-8 - Length
			 *    8-12 - Content Checksum
			 *    12-14 - CRC16 Checksum of the header.
			 *    */
			Byte[] data = null;
			using (MemoryStream mem = new MemoryStream(MessageHeaderLength)) {
				RPCWriter wr = new RPCWriter(mem);
				wr.Write(id);
				wr.Write((Byte)body);
				wr.Write((Byte)format);
				wr.Write(length);
				wr.Write(checksum);
				wr.Write((ushort)0);
				wr = null;

				data = mem.ToArray();
			}
			Byte[] headerChecksum = BitConverter.GetBytes(CRC16.ComputeChecksum(data, 0, MessageHeaderLength - 2));
			Array.Copy(headerChecksum, 0, data, MessageHeaderLength - 2, headerChecksum.Length);

			stream.Write(data, 0, data.Length);
		}
		/// <summary>
		/// Writes the message.
		/// </summary>
		/// <param name="stream">The stream.</param>
		/// <param name="message">The message.</param>
		protected void WriteMessage(Stream stream, RPCMessage message) {
			if (message == null) {
				throw new ArgumentNullException("message", "Can't be null.");
			}

			//1. Write User Content
			Byte[] contentData = null;
			UInt32 contentChecksum = 0;

			using (MemoryStream memoryStream = new MemoryStream()) {
				RPCWriter wr = new RPCWriter(memoryStream);

				message.WriteData(wr);
				contentData = memoryStream.ToArray();
				if ((message.Body & RPCBody.Checksum) == RPCBody.Checksum) {
					contentChecksum = CRC32.ComputeChecksum(contentData);
				}
				wr = null;
			}

			WriteHeader(stream, message.ID.ID, message.Body, message.Format, contentData.Length, contentChecksum);
			stream.Write(contentData, 0, contentData.Length);
		}
		protected bool ReadHeader(Byte[] data, ref UInt16 id, ref RPCBody body, ref RPCFormat format, ref Int32 length, ref UInt32 checksum) {
			if (data.Length == MessageHeaderLength) {
				id = BitConverter.ToUInt16(data, 0);
				body = (RPCBody)data[2];
				format = (RPCFormat)data[3];
				length = BitConverter.ToInt32(data, 4);
				checksum = BitConverter.ToUInt32(data, 8);

				return true;

			}
			return false;
		}
		protected RPCMessage CreateMessage(ushort id, RPCBody body, RPCFormat format, Byte[] data) {
			if (_types.ContainsKey(id)) {
				Type type = _types[id];
				Object obj = null;

				try {
					obj = Activator.CreateInstance(type);
				} catch {

				}
				if (obj != null && obj is RPCMessage) {
					RPCMessage msg = (RPCMessage)obj;

					msg.Format = format;
					msg.Body = body;
					using (MemoryStream memory = new MemoryStream(data,false)) {
						RPCReader reader = new RPCReader(memory);
						msg.ReadData(reader);
						reader = null;
					}
					return msg;
				}
			}
			return null;
		}
		#endregion
		#region Public
		/// <summary>
		/// Scans for rpc messages.
		/// </summary>
		public void Scan() {
			Assembly[] list = AppDomain.CurrentDomain.GetAssemblies();
			if (list != null && list.Length > 0) {
				foreach (Assembly asm in list) {
					Scan(asm);
				}
			}
		}
		/// <summary>
		/// Scans specified assembly for rpc messages.
		/// </summary>
		/// <param name="asm">The assembly.</param>
		public void Scan(Assembly asm) {
			Type[] types = asm.GetTypes();
			this.Scan(types);
		}
		/// <summary>
		/// Scans specified assembly for rpc messages.
		/// </summary>
		/// <param name="types">The types.</param>
		public void Scan(Type[] types) {
			if (types != null && types.Length > 0) {
				foreach (Type t in types) {
					this.Scan(t);
				}
			}
		}
		/// <summary>
		/// Scans specified assembly for rpc messages.
		/// </summary>
		/// <param name="type">The type.</param>
		public void Scan(Type type) {
			if (type.IsSubclassOf(typeof(RPCMessage))) {
				if (!type.IsAbstract && type.IsClass && !type.IsGenericType) {
					ushort id = RPCID.ParseID(type);
					if (id > 0) {
						_types[id] = type;
					}
				}
			}
		}

		/// <summary>
		/// Adds the handler. Used for events.
		/// </summary>
		/// <typeparam name="T">Type of message.</typeparam>
		/// <param name="method">The method.</param>
		public void AddHandler<T>(RPCInvoke<T> method) where T : RPCMessage {
			RPCID id = RPCID.ParseAttribute(typeof(T));

			if (id.HasID) {
				if (_events.ContainsKey(id.ID)) {
					_events[id.ID] = RPCInvoke<T>.Combine(_events[id.ID], method);
				} else {
					_events[id.ID] = method;
				}
			}
		}
		/// <summary>
		/// Removes the handler.
		/// </summary>
		/// <typeparam name="T">Type of message.</typeparam>
		/// <param name="method">The method.</param>
		public void RemoveHandler<T>(RPCInvoke<T> method) where T : RPCMessage {
			RPCID id = RPCID.ParseAttribute( typeof(T));

			if (id.HasID) {
				if (_events.ContainsKey(id.ID)) {
					_events[id.ID] = RPCInvoke<T>.Remove(_events[id.ID], method);
				}
			}
		}
		/// <summary>
		/// Clears all handlers
		/// </summary>
		public void ClearHandler() {
			_events.Clear();
		}
		/// <summary>
		/// Raises the event.
		/// </summary>
		/// <typeparam name="T">Type of message</typeparam>
		/// <param name="message">The message.</param>
		public void RaiseEvent<T>(T message) where T : RPCMessage {
			if (message == null) {
				throw new ArgumentNullException("message");
			}

			RPCID id = RPCID.ParseAttribute(typeof(T));
			if (_events.ContainsKey(id.ID)) {
				Delegate method = _events[id.ID];
				method.DynamicInvoke(message);
				
			}
			if (_eventReceived != null) {
				_eventReceived(message);
			}
		}
		/// <summary>
		/// Raises the event.
		/// </summary>
		/// <param name="message">The message.</param>
		public void RaiseEvent(RPCMessage message) {
			if (message == null) {
				throw new ArgumentNullException("message");
			}

			RPCID id = RPCID.ParseAttribute(message);
			if (_events.ContainsKey(id.ID)) {
				Delegate method = _events[id.ID];
				method.DynamicInvoke(message);
			}
			if (_eventReceived != null) {
				_eventReceived(message);
			}
		}

		/// <summary>
		/// Writes message to the stream.
		/// </summary>
		/// <param name="stream">The stream.</param>
		/// <param name="message">The message.</param>
		public void Write(Stream stream, RPCMessage message) {
			WriteMessage(stream, message);
		}
		/// <summary>
		/// Reads the message from the stream.
		/// </summary>
		/// <param name="stream">The stream.</param>
		/// <returns><see cref="RPCMessage"/> or <see cref="System.Null"/></returns>
		public RPCMessage Read(Stream stream) {
			Byte[] header = new Byte[MessageHeaderLength];
			Int32 offset = 0;
			Int32 count = header.Length;
			Boolean hasHeader = false;
			while (true) {
				int input = stream.ReadByte();

				if (input >= 0) {
					if (offset < header.Length) {
						header[offset] = (byte)input;
						offset += 1;
						if (offset == header.Length) {
							hasHeader = true;
						}
					} else {
						Array.Copy(header, 1, header, 0, header.Length - 1);
						header[offset - 1] = (byte)input;
					}

					if (hasHeader && ValidateHeader(header)) {

						ushort id = 0;
						RPCBody body =  RPCBody.None;
						RPCFormat format = RPCFormat.None;
						Int32 contentLength = 0;
						UInt32 contentChecksum = 0;

						if (ReadHeader(header, ref id, ref body, ref format, ref contentLength, ref contentChecksum)) {
							Int32 read = contentLength;
							Byte[] contentData = null;
							using (MemoryStream memory = new MemoryStream(read)) {
								while (read > 0) {
									Byte[] buffer = new Byte[(read < BufferSize ? read : BufferSize)];

									int r = stream.Read(buffer, 0, buffer.Length);

									memory.Write(buffer, 0, r);
									read -= r;
								}
								contentData = memory.ToArray();
							}
							Boolean checkChecksum = ((body & RPCBody.Checksum) == RPCBody.Checksum);
							if (!checkChecksum || (CRC32.ComputeChecksum(contentData) == contentChecksum)) {
								return this.CreateMessage(id, body, format, contentData);
							}
						}
					}
				} else {
					break;
				}
			}
			
			return null;
		}
		#endregion
		#region Static
		
		#endregion
		#region Events
		/// <summary>
		/// Occurs when message is received.
		/// </summary>
		public event RPCInvoke Received {
			add {
				_eventReceived = (RPCInvoke)RPCInvoke.Combine(_eventReceived, value);
			}
			remove {
				_eventReceived = (RPCInvoke)RPCInvoke.Remove(_eventReceived, value);
			}
		}
		#endregion
	}
}